#include <cutil_inline.h>
#include "config.cuh"

#define MODE 1
#if MODE==0
#define N 10000
#define DATAFILE "data.txt.origin"
#else
#define N 100000
#define DATAFILE "data.txt.10w"
#endif
#define CSC 10*N

#define timer_start(start, stop) \
	cudaEventCreate(&start);\
	cudaEventCreate(&stop);\
	cudaEventRecord(start ,0);
#define timer_stop(start, stop, elapsedTime) \
	cudaEventRecord(stop, 0);\
	cudaEventSynchronize(stop);\
	cudaEventElapsedTime(&elapsedTime, start, stop);\
	cudaEventDestroy(start);\
	cudaEventDestroy(stop);\
	cout<<elapsedTime<<"ms"<<endl;

void parseData(string rec, int* closeSetCount, int i, ha_t* ha)
{
	char * cstr, *p;
	cstr = new char[rec.size()+1];
	strcpy(cstr, rec.c_str());

	float pos = 0, duration = 0, speed = 0, time = 0;
	int baseId = 0, no = 0, etype = 0;

	p=strtok (cstr,"\t");
	no = atoi(p); //no
	p=strtok(NULL,"\t");
	time = (float)atof(p);//time
	p=strtok(NULL,"\t");
	baseId = atoi(p)-1; //baseid
	pos = baseId*2+1;//pos
	p=strtok(NULL,"\t");
	duration = (float)atof(p);//duration
	p=strtok(NULL,"\t");
	speed = (float)atof(p);//speed
	etype=INIT;

	int endBaseId;
	if(pos+duration*speed/3600>40)
		endBaseId=19;
	else
		endBaseId=(pos+duration*speed/3600)/2;

	ha->h_init[i]=*closeSetCount;
	ha->h_baseId[*closeSetCount]=baseId;
	ha->h_duration[*closeSetCount]=duration;
	ha->h_etype[*closeSetCount]=etype;
	ha->h_no[*closeSetCount]=no;
	ha->h_pos[*closeSetCount]=pos;
	ha->h_speed[*closeSetCount]=speed;
	ha->h_time[*closeSetCount]=time;
	*closeSetCount+=((endBaseId-baseId)*2+2);
}

void parseData2(string rec, int* closeSetCount, int i, ha_t* ha) {
	char * cstr, *p;
	cstr = new char[rec.size()+1];
	strcpy_s(cstr, rec.size()+1, rec.c_str());
	float pos = 0, duration = 0, speed = 0, time = 0;
	int baseId = 0, no = 0, etype = 0;

	p=strtok (cstr,"\t");
	no = atoi(p); //no
	//cout<<i<<" ";

	p=strtok(NULL,"\t");
	//cout<<p<<" ";
	time = (float)atof(p);//time

	p=strtok(NULL,"\t");
	duration = (float)atof(p);//duration

	p=strtok(NULL,"\t");
	pos = (float)atof(p);//pos
	baseId = (int)(pos/2);//baseid

	p=strtok(NULL,"\t");
	speed = (float)atof(p);//speed
	etype=INIT;

	int endBaseId;
	if(pos+duration*speed/3600>40)
		endBaseId=19;
	else
		endBaseId=(pos+duration*speed/3600)/2;

	ha->h_init[i]=*closeSetCount;
	ha->h_baseId[*closeSetCount]=baseId;
	ha->h_duration[*closeSetCount]=duration;
	ha->h_etype[*closeSetCount]=etype;
	ha->h_no[*closeSetCount]=no;
	ha->h_pos[*closeSetCount]=pos;
	ha->h_speed[*closeSetCount]=speed;
	ha->h_time[*closeSetCount]=time;
	*closeSetCount+=((endBaseId-baseId)*2+2);
	//cout<<*closeSetCount<<endl;
}

int init(ha_t* ha, da_t* da)
{
	ifstream fin;
	fin.open(DATAFILE);
	string rec;

	ha->h_baseId = (int*)malloc(CSC*sizeof(int));
	ha->h_duration = (float*)malloc(CSC*sizeof(float));
	ha->h_speed = (float*)malloc(CSC*sizeof(float));
	ha->h_pos = (float*)malloc(CSC*sizeof(float));
	ha->h_no = (int*)malloc(CSC*sizeof(int));
	ha->h_time = (float*)malloc(CSC*sizeof(float));
	ha->h_etype = (int*)malloc(CSC*sizeof(int));
	ha->h_init = (int*)malloc(N*sizeof(int));

	int i = 0;
	int counter = 0;
	while(!fin.eof()){
		getline(fin,rec);
		if(MODE == 0)
			parseData(rec, &counter, i, ha);
		else
			parseData2(rec, &counter, i, ha); 
		i++;
	}
	fin.close();

	cudaMalloc((void**)&(da->d_baseId), counter*sizeof(int));
	cudaMalloc((void**)&(da->d_time),counter*sizeof(float));
	cudaMalloc((void**)&(da->d_duration), counter*sizeof(float));
	cudaMalloc((void**)&(da->d_speed), counter*sizeof(float));
	cudaMalloc((void**)&(da->d_pos), counter*sizeof(float));
	cudaMalloc((void**)&(da->d_no), counter*sizeof(int));
	cudaMalloc((void**)&(da->d_etype), counter*sizeof(int));
	cudaMalloc((void**)&(da->d_init), N*sizeof(int));

	cudaMemcpy(da->d_time, ha->h_time, counter*sizeof(int), cudaMemcpyHostToDevice);
	cudaMemcpy(da->d_baseId,ha->h_baseId,counter*sizeof(int),cudaMemcpyHostToDevice);
	cudaMemcpy(da->d_duration,ha->h_duration,counter*sizeof(float),cudaMemcpyHostToDevice);
	cudaMemcpy(da->d_speed,ha->h_speed,counter*sizeof(float),cudaMemcpyHostToDevice);
	cudaMemcpy(da->d_pos,ha->h_pos,counter*sizeof(float),cudaMemcpyHostToDevice);
	cudaMemcpy(da->d_no,ha->h_no,counter*sizeof(int),cudaMemcpyHostToDevice);
	cudaMemcpy(da->d_etype,ha->h_etype,counter*sizeof(int),cudaMemcpyHostToDevice);
	cudaMemcpy(da->d_init,ha->h_init, N*sizeof(int), cudaMemcpyHostToDevice);

	free(ha->h_baseId);
	free(ha->h_duration);
	free(ha->h_etype);
	free(ha->h_no);
	free(ha->h_pos);
	free(ha->h_speed);

	return counter;
}

void debugParse(string rec, ha_t *ha, int i){
	char *cstr, *p;
	cstr = new char[rec.size()+1];
	strcpy(cstr, rec.c_str());

	int baseId = 0, no = 0, etype = 0;

	p=strtok (cstr,":");
	no = atoi(p); //no
	p=strtok(NULL,":");
	baseId = atoi(p); //baseid
	p=strtok(NULL,":");
	etype=atoi(p);//etype

	ha->h_baseId[i]=baseId;
	ha->h_etype[i]=etype;
	ha->h_no[i]=no;
}

int debugInit(ha_t *ha, da_t *da){
	ifstream fin;
	fin.open("data_after_sort.txt");
	string rec;
	getline(fin, rec);
	const int csc= atoi(rec.c_str());
	const int counter = csc;

	ha->h_baseId = (int*)malloc(csc*sizeof(int));
	ha->h_no = (int*)malloc(csc*sizeof(int));
	ha->h_etype = (int*)malloc(csc*sizeof(int));

	cudaMalloc((void**)&(da->d_baseId), csc*sizeof(int));
	cudaMalloc((void**)&(da->d_no), csc*sizeof(int));
	cudaMalloc((void**)&(da->d_etype), csc*sizeof(int));

	int i = 0;
	while(!fin.eof()){
		getline(fin,rec);
		debugParse(rec, ha, i);
		i++;
	}
	fin.close();

	for(int i=0; i<SAMPLENO; i++){
		cout<<ha->h_baseId[i]<<":"<<ha->h_no[i]<<":"<<ha->h_etype[i]<<"\t";
	}
	cout<<endl<<endl;

	cudaMemcpy(da->d_baseId,ha->h_baseId,counter*sizeof(int),cudaMemcpyHostToDevice);
	cudaMemcpy(da->d_no,ha->h_no,counter*sizeof(int),cudaMemcpyHostToDevice);
	cudaMemcpy(da->d_etype,ha->h_etype,counter*sizeof(int),cudaMemcpyHostToDevice);

	return counter;
}

int process(int* d_init, int* d_index, int* d_baseId, int* d_no, int* d_etype, 
	float* d_time, float* d_duration, float* d_speed, float* d_pos)
{
	for(int i=0; i<100; i++){
		int eidx = d_init[i];
		int no = d_no[eidx];
		float time = d_time[eidx];
		float duration = d_duration[eidx];
		float speed = d_speed[eidx];
		float pos = d_pos[eidx];

		float termiPos = pos+duration*speed/3600;
		if(termiPos>40)
			termiPos = 40;
		float currPos = pos;
		float nextPos = ((int)(currPos/2)+1)*2;
		if(i==3)
			cout<<"";
		cout<<i<<"\t"<<d_time[eidx]<<" ";
		while(nextPos<termiPos){
			eidx++;
			d_index[eidx] = eidx;
			d_time[eidx] = time = time+(nextPos-currPos)/speed*3600;
			d_speed[eidx] = speed;
			d_pos[eidx] = nextPos;
			d_no[eidx] = no;
			d_duration[eidx] = duration-(nextPos-pos)/speed*3600;
			d_etype[eidx] = HANDO;
			d_baseId[eidx] = nextPos/2;
			cout<<d_time[eidx]<<" ";
			currPos = nextPos;
			nextPos = nextPos+2;
		}
		eidx++;
		d_index[eidx] = eidx;
		d_time[eidx] = time+(termiPos-currPos)/speed*3600;
		d_speed[eidx] = speed;
		d_pos[eidx] = termiPos;
		d_no[eidx] = no;
		d_duration[eidx] = 0;
		d_etype[eidx] = TERMI;
		d_baseId[eidx] = currPos/2;
		cout<<d_time[eidx]<<endl;
	}
	return 0;
}

/*memcpy in launch kernel and main produce different effects possibly because
the extern indentifier*/
extern "C" void launch_init_kernel(da_t da, int count);
extern "C" void launch_sort_kernel(da_t &da, int count);
extern "C" void launch_separation_kernel(da_t da, int count, int *seps);
extern "C" void launch_sum_kernel(da_t da, int count, int ptr);
extern "C" int  launch_correction_kernel(da_t da, int base_n, int count, int ptr);


int main(int argc, char** argv)
{
	CUevent start, stop;
	CUevent start_o, stop_o;
	float elapsedTime;

	timer_start(start_o, stop_o);

	ha_t ha;
	da_t da;

	//INIT_HOST
	//timer_start(start, stop);
	int counter = init(&ha, &da);
	//cout<<"init host:\t";
	//timer_stop(start, stop, elapsedTime);

	//INIT_kernel after this process, d_speed, d_pos, d_duration, d_init is freed
	//timer_start(start, stop);
	launch_init_kernel(da, N);
	//cout<<"init kernel:\t";
	//timer_stop(start, stop, elapsedTime);

	//SORT_KERNEL after this process, d_time, d_index is freed
	//timer_start(start, stop);
	launch_sort_kernel(da, counter);
	//cout<<"sort:\t";
	//timer_stop(start, stop, elapsedTime);

	//SEP_KERNEL
	
	int *seps = (int*)malloc(21*sizeof(int));
	seps[0]=0;
	seps[20]=counter;
	//timer_start(start, stop);
	launch_separation_kernel(da, counter, seps);
	//debug print
	//for(int i=0; i<21; i++)
	//	cout<<seps[i]<<"\t";
	//cout<<endl<<"sep:\t";
	//timer_stop(start, stop, elapsedTime);

	//SUM_KERNEL and CORRECTION_KERNEL (find min and decrease) in a loop
	for(int i=0; i<20; i++){
		//cout<<i<<"\t";
		//SUM_KERNEL
		//timer_start(start, stop);
		launch_sum_kernel(da, seps[i+1]-seps[i], seps[i]);
		//cout<<"sum:\t";
		//timer_stop(start, stop, elapsedTime);
		//CORRECTION_KERNEL
		//cout<<"\tcorrection:\t";
		//timer_start(start, stop);
		int decptr = launch_correction_kernel(da, seps[i+1]-seps[i], counter, seps[i]);
		while(decptr != 0)
			decptr = launch_correction_kernel(da, seps[i+1]-seps[i], counter, seps[i]);
		//timer_stop(start, stop, elapsedTime);
	}
	cout<<"overall:\t";
	timer_stop(start_o, stop_o, elapsedTime);
}